
#ifndef __UsbComm_h__
#define __UsbComm_h__

#include "stdafx.h"
#include <math.h>
#include <map>

#include <Setupapi.h>
#define WM_RECEIVE_USB_PACKET (WM_USER+1)
#define WM_RECEIVE_USB_ERROR (WM_USER+2)
#define WM_WRITE_USB_ERROR (WM_USER+3)
#define WM_ABORT_READ_THREAD (WM_USER+4)
#define WM_WRITE_ASYNC (WM_USER+5)
#define WM_QUIT_FOR_UPDATE (WM_USER+7)

#include "ListCtrl.h"

#include "listtypes.h"
#include "resource.h"
#include "wininet.h"

#include <wakeup_usb_protocol.h>

#include "IRCReceiver.h"

static LPCTSTR g_regkey = _T("Software\\Cao Gadgets\\Wireless Tag Manager");
static LPCTSTR g_regkey_instances = _T("Software\\Cao Gadgets\\Wireless Tag Manager\\Instances");
static LPCTSTR g_regkey_cmdlines = _T("Software\\Cao Gadgets\\Wireless Tag Manager\\CmdLines");
static LPCTSTR g_autoRunKey = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
static LPCTSTR g_autoRunValue = _T("TagManager Launcher");
static LPCTSTR g_LauncherMutexName = _T("WirelessTagManagerLauncher");
static U8 slave_id_choices[254] = {
0x00, 0x5e, 0x61, 0xe2, 0x3f, 0xdd, 0x83, 
0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, 
0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, 
0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, 
0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xa0, 
0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62, 
0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 
0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xbc,
0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, 
0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07, 
0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, 
0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a, 
0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 
0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, 
0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, 
0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, 
0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, 
0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, 
0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, 
0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, 
0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, 
0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, 
0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, 
0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73, 
0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, 
0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, 
0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, 
0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, 
0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, 
0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8, 
0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, 
0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35
};


class CSlaveProfile
{
public:
	enum TagType {  tagBeeper=2, tagHmc=12, tagHmcArmed=13 };
	TagType m_tagType;
	bool m_hmc_rearm;
	int m_beepDurationDefault;

	CSlaveProfile()
	{
		m_beepDurationDefault=0;
		m_hmcOption.door_mode_delay=0;
		m_hmc_rearm=false;
		m_migrationPending=false;
		m_animationPhase=-1;
		m_Beeping = 0;
		m_fired=false;
		//m_autoBeep=false;
	}
	bool m_migrationPending;

	CSlaveProfile( bool alive, LPCTSTR name, LPCTSTR comment, int bat, int arssi, int id, TagType tagtype)
	{
		m_beepDurationDefault=0;
		m_migrationPending=false;
		//m_autoBeep=false;
		m_Alive=alive;
		m_Name=name;
		m_Comment=comment;
		m_batADCout=bat;
		m_ID=id;
		m_Beeping=false;
		m_animationPhase=-1;
		m_arssiADCout=arssi;
		m_tagType = tagtype;
		UpdateCommTime();

		if(tagtype==CSlaveProfile::tagHmc){
			// fill in default HMC options
			m_hmcOption.send_email=false;
			m_hmcOption.beep_pc=true;
			m_hmcOption.beep_pc_loop=false;
			m_hmcOption.beep_pc_vol=0xff;
			m_hmcOption.beep_tag=false;
			m_hmcOption.sensitivity=50;
			m_hmcOption.interval=2;
			m_hmcOption.door_mode=false;
			m_hmcOption.door_mode_delay=0;
			m_hmcOption.door_mode_angle=3;
		}
		m_beeperOption.beep_pc=true;
		m_beeperOption.beep_pc_vol=0xff;
		m_beeperOption.send_email=false;
		m_fired=false;
	}
	
	bool ReadIni(int num, CString& fn){		
		CString section;
		section.Format(_T("Tag%d"), num);

		m_ID = GetPrivateProfileInt(section, _T("ID"), 0xff, fn);
		if(m_ID==0xff)return false;

		GetPrivateProfileString(section, _T("Name"), _T("Unknown"), m_Name.GetBuffer(1024), 1023, fn);
		m_Name.ReleaseBuffer();
		GetPrivateProfileString(section, _T("Comment"), _T("Unknown"), m_Comment.GetBuffer(1024), 1023, fn);
		m_Comment.ReleaseBuffer();

		m_Alive = GetPrivateProfileInt(section, _T("Alive"), 0, fn);
		m_Beeping = GetPrivateProfileInt(section, _T("Beeping"), 0, fn);
		m_beepDurationDefault = GetPrivateProfileInt(section, _T("BDD"), 0, fn);
		m_migrationPending = GetPrivateProfileInt(section, _T("Migrating"), 0, fn);
		//m_autoBeep = GetPrivateProfileInt(section, _T("autoBeep"), 0, fn);
		m_batADCout = GetPrivateProfileInt(section, _T("batADCout"), 0, fn);
		m_arssiADCout = GetPrivateProfileInt(section, _T("arssiADCout"), 0, fn);
		
		FILETIME now;
		GetSystemTimeAsFileTime(&now);
		m_lastCommTime.dwHighDateTime = GetPrivateProfileInt(section, _T("lastCommHigh"), now.dwHighDateTime, fn);
		m_lastCommTime.dwLowDateTime = GetPrivateProfileInt(section, _T("lastCommLow"), now.dwLowDateTime, fn);

		m_tagType = (CSlaveProfile::TagType)GetPrivateProfileInt(section, _T("tagType"), tagBeeper, fn);
		if(m_tagType == tagHmc || m_tagType == tagHmcArmed)
		{
			GetPrivateProfileString(section, _T("hmc_email"), _T(""), m_hmcOption.email.GetBuffer(1024), 1023, fn);
			m_hmcOption.email.ReleaseBuffer();
			m_hmcOption.send_email = GetPrivateProfileInt(section, _T("hmc_send_email"), 0, fn);
			m_hmcOption.beep_pc = GetPrivateProfileInt(section, _T("hmc_beep_pc"), 1, fn);
			m_hmcOption.beep_pc_vol = GetPrivateProfileInt(section, _T("hmc_beep_pc_vol"), 0xff, fn);
			m_hmcOption.beep_pc_loop = GetPrivateProfileInt(section, _T("hmc_beep_pc_loop"), 0, fn);

			m_hmcOption.beep_tag = GetPrivateProfileInt(section, _T("hmc_beep_tag"), 0, fn);
			m_hmcOption.beep_tag_autostop = GetPrivateProfileInt(section, _T("hmc_beep_tag_autostop"), 1, fn);
			m_hmcOption.sensitivity = GetPrivateProfileInt(section, _T("hmc_sensitivity"), 50, fn);		
			m_hmcOption.interval = GetPrivateProfileInt(section, _T("hmc_interval"), 2, fn);		
			m_hmcOption.door_mode = GetPrivateProfileInt(section, _T("hmc_doormode"), 1, fn);		
			if(m_hmcOption.door_mode){
				m_hmcOption.door_mode_delay = GetPrivateProfileInt(section, _T("hmc_doordelay"), 0, fn);		
				m_hmcOption.door_mode_angle = GetPrivateProfileInt(section, _T("hmc_doorangle"), 3, fn);		
			
				TCHAR float_str[256];
				GetPrivateProfileString(section, _T("hmc_az_x"), _T("0.0"), float_str, 255, fn);
				m_hmcOption.az_x = _tstof(float_str);
				GetPrivateProfileString(section, _T("hmc_az_y"), _T("0.0"), float_str, 255, fn);
				m_hmcOption.az_y = _tstof(float_str);
				GetPrivateProfileString(section, _T("hmc_az_z"), _T("0.0"), float_str, 255, fn);
				m_hmcOption.az_z = _tstof(float_str);
			}
		}

		GetPrivateProfileString(section, _T("email"), _T(""), m_beeperOption.email.GetBuffer(1024), 1023, fn);
		m_beeperOption.email.ReleaseBuffer();
		m_beeperOption.send_email = GetPrivateProfileInt(section, _T("send_email"), 0, fn);
		m_beeperOption.beep_pc = GetPrivateProfileInt(section, _T("beep_pc"), 1, fn);
		m_beeperOption.beep_pc_vol = GetPrivateProfileInt(section, _T("beep_pc_vol"), 0xff, fn);

		return true;
	}

	void WriteIni(int num, CString& fn, bool backup=false){
		CString section;
		if(backup){

			section.Format(_T("Tag%d"), num);
			WritePrivateProfileString(section, NULL, NULL, fn);

			SYSTEMTIME t;
			GetLocalTime(&t);
			section.Format(_T("Tag%d_backup_%d_%d_%d_%d"),num, t.wYear, t.wMonth, t.wDay, GetTickCount());
		}else
			section.Format(_T("Tag%d"), num);
		
		CString data;
		data.Format(_T("%d"), m_ID);
		WritePrivateProfileString(section, _T("ID"), data, fn);
		WritePrivateProfileString(section, _T("Name"), m_Name, fn);
		WritePrivateProfileString(section, _T("Comment"), m_Comment, fn);
		data.Format(_T("%d"), m_Alive);
		WritePrivateProfileString(section, _T("Alive"), data, fn);
		data.Format(_T("%d"), m_Beeping);
		WritePrivateProfileString(section, _T("Beeping"), data, fn);
		
		data.Format(_T("%d"), m_beepDurationDefault);
		WritePrivateProfileString(section, _T("BDD"), data, fn);
		
		data.Format(_T("%d"), m_migrationPending);
		WritePrivateProfileString(section, _T("Migrating"), data, fn);
		

		/*data.Format(_T("%d"), m_autoBeep);
		WritePrivateProfileString(section, _T("autoBeep"), data, fn); */

		data.Format(_T("%d"), m_batADCout);
		WritePrivateProfileString(section, _T("batADCout"), data, fn);
		data.Format(_T("%d"), m_arssiADCout);
		WritePrivateProfileString(section, _T("arssiADCout"), data, fn);

		data.Format(_T("%d"), m_lastCommTime.dwHighDateTime);
		WritePrivateProfileString(section, _T("lastCommHigh"), data, fn);
		data.Format(_T("%d"), m_lastCommTime.dwLowDateTime);
		WritePrivateProfileString(section, _T("lastCommLow"), data, fn);

		data.Format(_T("%d"),m_tagType);
		WritePrivateProfileString(section, _T("tagType"), data, fn);
		if(m_tagType == tagHmc || m_tagType == tagHmcArmed)
		{
			WritePrivateProfileString(section, _T("hmc_email"), m_hmcOption.email, fn);
			data.Format(_T("%d"), m_hmcOption.send_email);
			WritePrivateProfileString(section, _T("hmc_send_email"), data, fn);
			data.Format(_T("%d"), m_hmcOption.beep_pc);
			WritePrivateProfileString(section, _T("hmc_beep_pc"), data, fn);
			data.Format(_T("%d"), m_hmcOption.beep_pc_loop);
			WritePrivateProfileString(section, _T("hmc_beep_pc_loop"), data, fn);
			data.Format(_T("%d"), m_hmcOption.beep_pc_vol);
			WritePrivateProfileString(section, _T("hmc_beep_pc_vol"), data, fn);
			data.Format(_T("%d"), m_hmcOption.beep_tag);
			WritePrivateProfileString(section, _T("hmc_beep_tag"), data, fn);
			data.Format(_T("%d"), m_hmcOption.beep_tag_autostop);
			WritePrivateProfileString(section, _T("hmc_beep_tag_autostop"), data, fn);			
			data.Format(_T("%d"), m_hmcOption.sensitivity);
			WritePrivateProfileString(section, _T("hmc_sensitivity"), data, fn);
			data.Format(_T("%d"), m_hmcOption.interval);
			WritePrivateProfileString(section, _T("hmc_interval"), data, fn);
			data.Format(_T("%d"), m_hmcOption.door_mode);
			WritePrivateProfileString(section, _T("hmc_doormode"), data, fn);
			if(m_hmcOption.door_mode)
			{
				data.Format(_T("%d"), m_hmcOption.door_mode_delay);
				WritePrivateProfileString(section, _T("hmc_doordelay"), data, fn);
			
				data.Format(_T("%d"), m_hmcOption.door_mode_angle);
				WritePrivateProfileString(section, _T("hmc_doorangle"), data, fn);
			
				data.Format(_T("%f"), m_hmcOption.az_x);
				WritePrivateProfileString(section, _T("hmc_az_x"), data, fn);
				data.Format(_T("%f"), m_hmcOption.az_y);
				WritePrivateProfileString(section, _T("hmc_az_y"), data, fn);
				data.Format(_T("%f"), m_hmcOption.az_z);
				WritePrivateProfileString(section, _T("hmc_az_z"), data, fn);			
			}
		}
		WritePrivateProfileString(section, _T("email"), m_beeperOption.email, fn);
		data.Format(_T("%d"), m_beeperOption.send_email);
		WritePrivateProfileString(section, _T("send_email"), data, fn);
		data.Format(_T("%d"), m_beeperOption.beep_pc);
		WritePrivateProfileString(section, _T("beep_pc"), data, fn);
		data.Format(_T("%d"), m_beeperOption.beep_pc_vol);
		WritePrivateProfileString(section, _T("beep_pc_vol"), data, fn);
	}

	//1.2/2.2*256= 
	int percentBatteryRemaining(){
		int percentage = (int)((batteryVoltageVolt()-2.4)/0.7*100);
		if(percentage<0)percentage=0;
		//if(percentage>100)percentage=100;
		return percentage;
	}
	CString percentBatteryRemainingT(){
		CString a;
		a.Format(_T("%d"),percentBatteryRemaining());
		return a;
	}
	float batteryVoltageVolt(){
		return 2.1/((float)m_batADCout/256.0); 
	}
	int signalStrengthIndBm(){
		if(m_Alive)
			return (int)(((m_arssiADCout/256.0*2.048)-0.45)*(100-65)/(1.15-0.45)-100);
		else
			return -110;
	}
	CString signalStrengthIndBmT(){
		CString a;
		if(signalStrengthIndBm()>-100 && m_Alive)
			a.Format(_T("%ddBm"),signalStrengthIndBm());
		else
			a.Format(_T("No signal"));
		return a;
	}
	DWORD  secondsOld() const{
		FILETIME now;
		GetSystemTimeAsFileTime(&now);
		LARGE_INTEGER now_li, then_li;
		now_li.HighPart = now.dwHighDateTime;
		now_li.LowPart = now.dwLowDateTime;
		then_li.HighPart = m_lastCommTime.dwHighDateTime;
		then_li.LowPart = m_lastCommTime.dwLowDateTime;
		return (DWORD)floor((now_li.QuadPart - then_li.QuadPart) / 1e7 + 0.5);
	}
	CString ageText(){
		double sec = (double)secondsOld();
		CString a;
		//a.Format(_T("%d"), (int)sec);
		
		if(sec > 3600*24){
			a.Format(_T("%d day ago"), (int)floor(sec/3600/24));// , (int)floor((sec-floor(sec/3600/24)*3600*24)/3600+0.5));
		}else if(sec > 3600){
			a.Format(_T("%d hour ago"), (int)floor(sec/3600) ); //, (int)floor( (sec-floor(sec/3600)*3600)/60+0.5 ));
		}else if(sec > 60){
			a.Format(_T("%d minute ago"), (int)floor(sec/60) ); //, (int)(sec-60*floor(sec/60)), (int)sec);
		}else{
			a.Format(_T("%d second ago"), (int)sec ); //, (int)(sec-60*floor(sec/60)), (int)sec);
		}
		return a;
	}
	void UpdateCommTime(){
		GetSystemTimeAsFileTime(&m_lastCommTime);
	}
	int m_arssiADCout;			// 8bit ADC digitizing 450mV(-100dBm)-1150mV(-65dBm) using 2.048V reference
	bool m_Alive;
	bool m_Beeping; 
	//bool m_autoBeep;

	CString m_Name;
	CString m_Comment;
	int m_batADCout;			// 8bit ADC digitizing 1.2V reference using VDD as FS
	int m_ID;
	int m_animationPhase; 
	FILETIME m_lastCommTime;
	bool m_fired;		// volatile, not saved

	static double Angle_Index2Radian[9];
	static int Delay_Index2Seconds[10];

	typedef struct {
		CString email;
		bool send_email;
		int sensitivity;		// from 0 to 100
		int interval; 
		bool beep_pc, beep_pc_loop;
		int beep_pc_vol;
		bool beep_tag, beep_tag_autostop;
		bool door_mode;
		int door_mode_delay;
		int door_mode_angle;

		inline double DoorModeAngleInRadian(){
			return CSlaveProfile::Angle_Index2Radian[door_mode_angle];
		}
		inline int DoorModeDelayInSeconds(){
			return CSlaveProfile::Delay_Index2Seconds[door_mode_delay];
		}

		void FillInPacket(UsbPacket_OUT* packet){
			packet->extra.motion_sensor.interval_count = interval;
			packet->extra.motion_sensor.diff_threshold = 5*exp((100-sensitivity)*0.0393);  // from 5 to 255  //0x10;
			packet->extra.motion_sensor.flags=0;
			//if(beep_tag)packet->extra.motion_sensor.flags |= MOTION_SENSOR_OPTION_BEEP_ON_MOVE;
			if(door_mode)packet->extra.motion_sensor.flags |= MOTION_SENSOR_OPTION_SEND_NEXT_READING;
		}

		double az_x, az_y, az_z;
	} HmcOption; 
	HmcOption m_hmcOption;
	
	typedef struct {
		CString email;
		bool send_email;
		bool beep_pc;
		int beep_pc_vol;
	} BeeperOption; 
	BeeperOption m_beeperOption;
};

class CVersionNotificationDialog : public CDialogImpl<CVersionNotificationDialog>
{
public:
	enum { IDD = IDD_VERSIONNOTIFY };

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(IDOK, OnOK)
		COMMAND_ID_HANDLER(IDCANCEL, OnCancel)		
	END_MSG_MAP()

	CString m_destFileName;

	LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		int ret=0;
		CWindow wndStatus = GetDlgItem(IDC_STATIC_STATUS);
		EnableWindow(FALSE);
		HINTERNET hINet, hFile;
		hINet = InternetOpen("WirelessTagManager", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0 );
		if ( !hINet )
		{
			MessageBox(_T("Cannot connect to Internet"), _T("Wireless Tag Manager"), MB_ICONWARNING);
			EndDialog(0);
			return 0;
		}
		GetTempPath(1024, m_destFileName.GetBuffer(1024));
		m_destFileName.ReleaseBuffer();
		m_destFileName.TrimRight(_T('\\'));
		m_destFileName.Format(_T("%s\\TagManagerUpdate_ver.%d.%d\\"), m_destFileName, m_verh, m_verl);
		
		CreateDirectory(m_destFileName, NULL);
		//CString fn; fn.Format(_T("WirelessTagManager_%x.exe"), GetTickCount());
		//m_destFileName.Append(fn);

		CString thisExe; 
		GetModuleFileName(NULL, thisExe.GetBuffer(1024),1019);
		thisExe.ReleaseBuffer();
		
		m_destFileName.Append(thisExe.Right(thisExe.GetLength() - thisExe.ReverseFind(_T('\\')) - 1));
		
		HANDLE hDest = CreateFile(m_destFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
		if(INVALID_HANDLE_VALUE==hDest){
			CString a; a.Format(_T("Failed to write to %s. Please manually remove this file."), m_destFileName);
			MessageBox(a, _T("Wireless Tag Manager"), MB_ICONWARNING);
			EndDialog(0);
			return 0;
		}

		hFile = InternetOpenUrl( hINet, m_url, NULL, 0, 0, 0 );
		int kb_downloaded=0;
		if ( hFile )
		{
			CHAR buffer[4096];
			DWORD dwRead;
			while ( InternetReadFile( hFile, buffer, 4096, &dwRead ) )
			{
				if ( dwRead == 0 )
					break;
				
				DWORD written;
				WriteFile(hDest, buffer, dwRead, &written, NULL);
				
				kb_downloaded+=dwRead/1024;

				CString status; status.Format(_T("Downloaded %dkB"), kb_downloaded);
				wndStatus.SetWindowText(status);

				MSG msg;
				while(::PeekMessage(&msg, m_hWnd, 0,0, PM_REMOVE)){
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
				ret = 1;
			}
			InternetCloseHandle( hFile );
			CloseHandle(hDest);
		}
		InternetCloseHandle( hINet );
		
		EndDialog(ret);
		return 0;
	}

	LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		EndDialog(0);
		return 0;
	}

	ATL::CRegKey m_regKey;
	CString m_msg, m_url;
	int m_verh, m_verl;

	CVersionNotificationDialog(CString msg, CString downloadURL, int version_high, int version_low) : 
		m_msg(msg), m_url(downloadURL), m_verh(version_high), m_verl(version_low) 
	{
		
		LONG result = m_regKey.Open(HKEY_CURRENT_USER, g_regkey);
		if(result != ERROR_SUCCESS)
		{
			result = m_regKey.Create(HKEY_CURRENT_USER, g_regkey);
		}
		if(result == ERROR_SUCCESS)
		{
			if(ERROR_SUCCESS != m_regKey.QueryDWORDValue(_T("autoCheckUpdate"), (DWORD&)m_autoCheckUpdate))
			{
				m_autoCheckUpdate=true;
			}
		}
	}
	~CVersionNotificationDialog(){
		m_regKey.Close();
	}

	bool m_autoCheckUpdate;
	CButton m_cbDontShow;

	LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		SetForegroundWindow(m_hWnd);
		m_cbDontShow.Attach(GetDlgItem(IDC_DONTSHOW));
		m_cbDontShow.SetCheck(m_autoCheckUpdate? BST_UNCHECKED:BST_CHECKED);
		::SetWindowText(GetDlgItem(IDC_NEWVERSION_DESC), m_msg.GetString());
		return TRUE;
	}
	LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		/*if(m_autoCheckUpdate == true && m_cbDontShow.GetCheck() == BST_CHECKED){
			MessageBox(_T("You can still check update from the version information window"), _T("Information"), MB_OK|MB_ICONINFORMATION);
		}*/
		m_autoCheckUpdate = (m_cbDontShow.GetCheck() != BST_CHECKED);
		m_regKey.SetDWORDValue(_T("autoCheckUpdate"), m_autoCheckUpdate ? 1:0);
		return 0;
	}
};

class CAboutDialog : public CDialogImpl<CAboutDialog>
{
public:
	enum { IDD = IDD_ABOUTBOX };

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
		COMMAND_ID_HANDLER(IDOK, OnOK)
		COMMAND_ID_HANDLER(IDCANCEL, OnCancel)		
	END_MSG_MAP()

	LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		EndDialog(wID);
		return 0;
	}

	LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		EndDialog(wID);
		return 0;
	}

	static void GetVersionDWORD(DWORD& ms, DWORD& ls){
		CString fn;
		GetModuleFileName(NULL, fn.GetBuffer(1024),1019);
		fn.ReleaseBuffer();

		DWORD dwDummy; 
		DWORD dwFVISize = GetFileVersionInfoSize( fn, &dwDummy ); 
		LPBYTE lpVersionInfo = new BYTE[dwFVISize]; 
		GetFileVersionInfo( fn , 0 , dwFVISize , lpVersionInfo ); 
		UINT uLen; 
		VS_FIXEDFILEINFO *lpFfi; 
		VerQueryValue( lpVersionInfo , _T("\\") , (LPVOID *)&lpFfi , &uLen ); 
		ms = lpFfi->dwFileVersionMS; 
		ls = lpFfi->dwFileVersionLS; 
		delete [] lpVersionInfo; 		
	}

	LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		CString version_text;
		DWORD ms, ls;
		GetVersionDWORD(ms,ls);

		DWORD dwLeftMost = HIWORD(ms); 
		DWORD dwSecondLeft = LOWORD(ms); 
		DWORD dwSecondRight = HIWORD(ls); 
		DWORD dwRightMost = LOWORD(ls); 		
		version_text.Format( _T("Version: %d.%d.%d.%d") , dwLeftMost, dwSecondLeft, 
			dwSecondRight, dwRightMost ); 


		GetDlgItem(IDC_VERSION_TEXT).SetWindowText(version_text);

		return TRUE;
	}
};

class CCaoGadgetsDotCom;

class CThisMap{
	std::map<HINTERNET, CCaoGadgetsDotCom*> m_thisMap; 
	CRITICAL_SECTION m_thisMapLock;

public:
	CThisMap(){
		InitializeCriticalSection(&m_thisMapLock);
	}
	~CThisMap(){
		//DeleteCriticalSection(&m_thisMapLock);
	}
	void Add(HINTERNET hi, CCaoGadgetsDotCom* p){
		EnterCriticalSection(&m_thisMapLock);
		m_thisMap[hi]=p;
		LeaveCriticalSection(&m_thisMapLock);
	}
	typedef std::map<HINTERNET, CCaoGadgetsDotCom*>::iterator iterator;

	iterator Get(HINTERNET hInternet)
	{
		EnterCriticalSection(&m_thisMapLock);
		iterator it = m_thisMap.find(hInternet);
		LeaveCriticalSection(&m_thisMapLock);
		if(it==m_thisMap.end())
			return NULL;
		else
			return it;
	}
	void Remove(iterator it){
		EnterCriticalSection(&m_thisMapLock);
		m_thisMap.erase(it);
		LeaveCriticalSection(&m_thisMapLock);
	}
};

class CCaoGadgetsDotCom
{
	static CThisMap m_thisMap;
	ATL::CEvent m_waitHandle;

	static CString JsonEscape(CString raw)
	{
		raw.Replace(_T("\\"),_T("\\\\"));
		raw.Replace(_T("\""),_T("\\\""));
		raw.Replace(_T("'"),_T("\\'"));
		raw.Replace(_T("\n"),_T("\\n"));
		raw.Replace(_T("\r"),_T("\\r"));
		raw.Replace(_T("\t"),_T("\\t"));
		return raw;
	}
public: 
	
	static CString WindowsErrorMessage(DWORD error){
		LPVOID lpMsgBuf;

		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM,
			NULL,
			error,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPTSTR) &lpMsgBuf,
			0, NULL );

		CString msg;
		msg.Format(_T("Internet connection failed with error %d: %s"), error, lpMsgBuf); 
		//MessageBox(NULL,msg,_T("Error"),MB_OK);

		LocalFree(lpMsgBuf);
		return msg;
	}

	HINTERNET    m_hInternetSession, m_hConnect, m_hRequest;
	HWND    m_hwnd;
	TCHAR  m_serverReply[512];
	INTERNET_BUFFERS m_inetBuf;

	enum AsmxCmd {Login=1, Logout=2, Update=3, UpdateAll=4};
	AsmxCmd m_asmxCmd;

	static void __stdcall InternetCallbackFunction_Emailer(HINTERNET hInternet,
		DWORD dwContext,
		DWORD dwInternetStatus,
		LPVOID lpvStatusInformation,
		DWORD dwStatusInformationLength)
	{
		INTERNET_ASYNC_RESULT* res;
		
		CThisMap::iterator it = m_thisMap.Get(hInternet); 
		if(it==NULL)return;
		CCaoGadgetsDotCom* p=it->second;

		switch (dwInternetStatus)
		{
		case INTERNET_STATUS_HANDLE_CREATED:
			// get the handle now that it has been created so it can be freed up later
			res = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
			if(dwContext == 1){
				p->m_hConnect = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hConnect, p);
			}
			else if(dwContext == 2){
				p->m_hRequest = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hRequest, p);
			}
			break;

		case INTERNET_STATUS_REQUEST_COMPLETE:
			if(!p->m_hRequest)break;
			
			char buf[128];
			DWORD szBuf=sizeof(buf);
			if(!HttpQueryInfo(p->m_hRequest, HTTP_QUERY_STATUS_CODE, buf, &szBuf, NULL)){
				CString msg;
				msg.Format(_T("Email not sent: %s"), WindowsErrorMessage(GetLastError()).GetString());
				::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)msg.GetString());
			}else if(strcmp(buf, "200") && strcmp(buf, "302")){
				CString msg;
				msg.Format(_T("Email not sent: HTTP status %s"), buf);
				::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)msg.GetString());				
			}else{
				::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Email sent"));				
			}

			delete p;
		}
	}
		
	static void __stdcall InternetCallbackFunction_CheckUpdate(HINTERNET hInternet,
		DWORD dwContext,
		DWORD dwInternetStatus,
		LPVOID lpvStatusInformation,
		DWORD dwStatusInformationLength)
	{
		INTERNET_ASYNC_RESULT* res;
		
		CThisMap::iterator it = m_thisMap.Get(hInternet); 
		if(it==NULL)return;
		CCaoGadgetsDotCom* p=it->second;
		
		switch (dwInternetStatus)
		{
		case INTERNET_STATUS_HANDLE_CREATED:
			// get the handle now that it has been created so it can be freed up later
			res = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
			
			if(dwContext == 1){
				p->m_hConnect = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hConnect, p);
			}
			else if(dwContext == 2){
				p->m_hRequest = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hRequest, p);
			}

			break;

		case INTERNET_STATUS_REQUEST_COMPLETE:
			if(p->m_hRequest ==NULL)break;

			if(dwContext==2 )
			{
				if(!InternetReadFileEx(p->m_hRequest, &(p->m_inetBuf), IRF_ASYNC, 3)){
					DWORD err = GetLastError();
					if(err!=ERROR_IO_PENDING){
						if(IsWindowEnabled(m_hwndStatus)){
							CString msg; msg.Format(_T("Could not open Internet to check version update: %s(%d)"), WindowsErrorMessage(err), err);
							::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)msg.GetString());		
						}

						delete p;

						break;
					}else{
						break;
					}
				}
			}

			DWORD ms, ls, ms_latest, ls_latest;
			TCHAR version_note[256], download_url[256];
			CAboutDialog::GetVersionDWORD(ms,ls);

			int numRead;
#ifdef _UNICODE
			numRead=_snwscanf(p->m_serverReply, 511, _T("%x %x %255[^#] %255s"), &ms_latest, &ls_latest, version_note, download_url);
#else
			numRead=_snscanf(p->m_serverReply, 511, _T("%x %x %255[^#] %255s"), &ms_latest, &ls_latest, version_note, download_url);
#endif
			if(numRead!=4)break;

			if(ms_latest != ms || ls_latest != ls){
				CVersionNotificationDialog dlg(version_note, download_url+1, ms_latest, ls_latest);
				if(p->m_showUpdateNotification || dlg.m_autoCheckUpdate)
				{
					if(dlg.DoModal(p->m_hwnd)){

						STARTUPINFO si;
						PROCESS_INFORMATION pi;
						ZeroMemory( &si, sizeof(si) );
						si.cb = sizeof(si);
						ZeroMemory( &pi, sizeof(pi) );
						CString cmdline; cmdline.Format(_T("\"%s\" /Update"), dlg.m_destFileName);
						CreateProcess(dlg.m_destFileName, cmdline.GetBuffer(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
						CloseHandle( pi.hProcess );
						CloseHandle( pi.hThread );

						while(1){
							HWND hwnd = FindWindow(_T("WirelessTagManagerClass"), NULL);
							if(hwnd!=NULL){
								//MessageBox(NULL,"About to send wm_command",NULL,0);
								//::EndDialog(hwnd, IDC_QUIT_FOR_UPDATE);
								SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_QUIT_FOR_UPDATE,0), 0);
								//SendMessage(hwnd, WM_QUIT_FOR_UPDATE, 0, 0);
							}else
								break;
						}

						//::ShellExecute(p->m_hwnd, _T("open"), download_url+1, NULL, NULL, SW_SHOWNORMAL);

						//::ShellExecute(p->m_hwnd, _T("open"), dlg.m_destFileName, arg, NULL, SW_SHOWNORMAL);

					}
				}	
			}else{
				if(IsWindowEnabled(m_hwndStatus))
					::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("This version is up to date."));	
			}
			//MessageBox(NULL, p->m_serverReply, _T("server reply"), MB_OK);

			delete p;
			break;
			
		}
	}
	
	bool m_showUpdateNotification;

	static void __stdcall InternetCallbackFunction_ASMX(HINTERNET hInternet,
		DWORD dwContext,
		DWORD dwInternetStatus,
		LPVOID lpvStatusInformation,
		DWORD dwStatusInformationLength)
	{
		INTERNET_ASYNC_RESULT* res;
		
		CThisMap::iterator it = m_thisMap.Get(hInternet); 
		if(it==NULL)return;
		CCaoGadgetsDotCom* p=it->second;

		switch (dwInternetStatus)
		{
		case INTERNET_STATUS_HANDLE_CREATED:
			// get the handle now that it has been created so it can be freed up later
			res = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
			if(dwContext == 1){
				p->m_hConnect = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hConnect, p);
			}
			else if(dwContext == 2){
				p->m_hRequest = (HINTERNET)(res->dwResult);
				m_thisMap.Add(p->m_hRequest, p);
			}

			break;

		case INTERNET_STATUS_REQUEST_COMPLETE:
			if(p->m_hRequest==NULL)break;

			if(dwContext==2 )
			{
				if(!InternetReadFileEx(p->m_hRequest, &(p->m_inetBuf), IRF_ASYNC, 3)){
					DWORD err = GetLastError();
					if(err!=ERROR_IO_PENDING){
						if(IsWindowEnabled(m_hwndStatus)){
							CString msg; msg.Format(_T("mytaglist.com connect error: %s(%d)"), WindowsErrorMessage(err), err);
							::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)msg.GetString());		
						}

						delete p;
						break;
					}else{
						break;
					}
				}
			}

			int numRead;
			if(p->m_asmxCmd == AsmxCmd::Login){
	
				//{"nick":"emhpaGVuZ2Nhb0B","port":6667,"server":"gameservers.us.quakenet.org"}
				CString nick, server;
				int port;
#ifdef _UNICODE
				numRead=_snwscanf(p->m_serverReply, 511, _T("{\"d\":{\"__type\":\"MyTagList.IRCConnectInformation\",\"nick\":\"%20[^\"]\",\"port\":%d,\"server\":\"%255[^\"]\""), 
					nick.GetBuffer(32), &port, server.GetBuffer(256));
#else
				numRead=_snscanf(p->m_serverReply, 511, _T("{\"d\":{\"__type\":\"MyTagList.IRCConnectInformation\",\"nick\":\"%20[^\"]\",\"port\":%d,\"server\":\"%255[^\"]\""), 
					nick.GetBuffer(32), &port, server.GetBuffer(256));
#endif
				server.ReleaseBuffer();
				nick.ReleaseBuffer();
				if(numRead!=3){
					TCHAR szStatusCode[32];
					DWORD dwInfoSize = 32;
					HttpQueryInfo(p->m_hRequest, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwInfoSize, NULL);
					long nStatusCode = _ttol(szStatusCode);

					CString msg;
					msg.Format(_T("%s\n(Http Status %d)"), p->m_serverReply, nStatusCode);
					MessageBox(NULL, msg, _T("Mytaglist.com returned error"), MB_ICONWARNING|MB_SETFOREGROUND);
					::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Failed to connect with mytaglist.com"));
				}else{
					if(m_ircReceiver.IsConnected())m_ircReceiver.Disconnect();
					if(!m_ircReceiver.Connect(server, port, nick)){
						::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Failed to connect with mytaglist.com"));	
					}
				}
			}else if(p->m_asmxCmd == AsmxCmd::Logout){

				if(m_ircReceiver.IsConnected())m_ircReceiver.Disconnect();
				
			}else if(p->m_asmxCmd == AsmxCmd::Update){
				
				if(m_ircReceiver.SendReply(p->m_tag_id, p->m_tag_cmd, _T("SyncUpdate"))){
					
					if(p->m_tag_cmd != CMD_LISTEN_FOR_ONE_EVENT)
						::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Executed a command from mytaglist.com"));

				}else{
					::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Uploaded a tag information to mytaglist.com"));
				}

			}else if(p->m_asmxCmd == AsmxCmd::UpdateAll){

				if(m_ircReceiver.SendReply(p->m_tag_id, p->m_tag_cmd, _T("SyncUpdateAll"))){
					::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Executed a command from mytaglist.com"));
				}else{
					::PostMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)_T("Uploaded all tag information to mytaglist.com"));
				}

			}
			
			p->m_waitHandle.Set();
			delete p;

			break;
		}
	}

public:
	static HWND m_hwndStatus;
	static bool m_enableEmails;
	static CIRCReceiver m_ircReceiver;

	CCaoGadgetsDotCom() : m_waitHandle(TRUE, FALSE){
		m_showUpdateNotification=false;

		ZeroMemory(&m_inetBuf, sizeof(m_inetBuf));
		m_inetBuf.dwStructSize=sizeof(INTERNET_BUFFERS);
		m_inetBuf.lpvBuffer = m_serverReply;
		m_inetBuf.dwBufferLength = sizeof(m_serverReply)/sizeof(TCHAR);
		ZeroMemory(m_serverReply, sizeof(m_serverReply));

		m_hInternetSession = InternetOpen(_T("WirelessTagManager"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC);
		m_hConnect=0;
		m_hRequest=0;
	}
	~CCaoGadgetsDotCom(){
		InternetSetStatusCallback(m_hInternetSession, NULL);

		CThisMap::iterator it;

		if(m_hRequest){
			it = m_thisMap.Get(m_hRequest);
			if(it!=NULL)m_thisMap.Remove(it);
			InternetCloseHandle(m_hRequest);
		}
		if(m_hConnect){
			it = m_thisMap.Get(m_hConnect);
			if(it!=NULL)m_thisMap.Remove(it);
			InternetCloseHandle(m_hConnect);
		}
		
		it = m_thisMap.Get(m_hInternetSession);
		if(it!=NULL)m_thisMap.Remove(it);
		InternetCloseHandle(m_hInternetSession);
	}
	
	CString data;

	void ManagerLoginOrUpdateAll(bool islogin, CString& email, CString& pwd, CListArray < CSlaveProfile >& taglist, int tag_cmd=0)
	{
		m_tag_cmd = tag_cmd;
		m_tag_id = 0xFF;

		m_asmxCmd = islogin? AsmxCmd::Login : AsmxCmd::UpdateAll;

		m_thisMap.Add(m_hInternetSession, this);

		InternetSetStatusCallback(m_hInternetSession,
                        (INTERNET_STATUS_CALLBACK)InternetCallbackFunction_ASMX);

#ifdef USE_Z560
		m_hConnect = InternetConnect(m_hInternetSession, _T("localhost"),Z560_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#else
		m_hConnect = InternetConnect(m_hInternetSession, _T("www.mytaglist.com"),INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#endif
		m_hRequest = HttpOpenRequest(m_hConnect, _T("POST"), 
			islogin ? _T("/taglistManager.asmx/ManagerLogin") : _T("/taglistManager.asmx/ManagerUpdateAll"),
			NULL, NULL, NULL, INTERNET_FLAG_NO_UI, 2);
		
		static TCHAR hdrs[] =_T("Content-Type: application/json; charset=utf-8");
		data.Format(_T("{email:'%s',password:'%s',taglist: ["), email.GetString(), pwd.GetString());

		CString tag;
		for(int i=0;i<taglist.GetSize();i++){
			if(i!=0)
				data.Append(_T(","));
			CSlaveProfile& profile = taglist[i];
			
			tag.Format(_T("{'battery':%f,'signaldBm':%d,'comment':'%s','fired':%s,'doormode':%s,'beeping':%s,'id':%d,'lastComm':%I64u,'name':'%s','typeid':%d,'beepOptionDefault':%d}"),
				profile.batteryVoltageVolt(), profile.signalStrengthIndBm(), 
				JsonEscape(profile.m_Comment).GetString(), profile.m_fired?_T("true"):_T("false"), profile.m_hmcOption.door_mode?_T("true"):_T("false"),
				profile.m_Beeping?_T("true"):_T("false"),
				profile.m_ID, 
				static_cast<__int64>(profile.m_lastCommTime.dwHighDateTime) << 32 | profile.m_lastCommTime.dwLowDateTime, 
				JsonEscape(profile.m_Name).GetString(), profile.m_tagType, profile.m_beepDurationDefault);
			data.Append(tag);
		}

		data.Append(_T("]}"));
		
		HttpSendRequest(m_hRequest, hdrs, strlen(hdrs), data.GetBuffer(), data.GetLength());
		
		DWORD err = GetLastError();
		if(err != ERROR_IO_PENDING && err != ERROR_SUCCESS)
		{
			//MessageBox(CCaoGadgetsDotCom::WindowsErrorMessage(err), _T("Error connecting to www.mytaglist.com"), MB_ICONWARNING);
			::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)WindowsErrorMessage(err).GetString());
			return;
		}
		
	}
	
	void ManagerLogout(CString& email, CString& pwd, bool wait=false, DWORD timeout=INFINITE)
	{
		m_asmxCmd = AsmxCmd::Logout;

		m_thisMap.Add(m_hInternetSession, this);

		InternetSetStatusCallback(m_hInternetSession,
                        (INTERNET_STATUS_CALLBACK)InternetCallbackFunction_ASMX);

#ifdef USE_Z560
		m_hConnect = InternetConnect(m_hInternetSession, _T("localhost"), Z560_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#else
		m_hConnect = InternetConnect(m_hInternetSession, _T("www.mytaglist.com"),INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#endif
		m_hRequest = HttpOpenRequest(m_hConnect, _T("POST"), 
			_T("/taglistManager.asmx/ManagerLogout"),
			NULL, NULL, NULL, INTERNET_FLAG_NO_UI, 2);
		
		static TCHAR hdrs[] =_T("Content-Type: application/json; charset=utf-8");
		data.Format(_T("{email:'%s',password:'%s'}"), email.GetString(), pwd.GetString());
		
		HttpSendRequest(m_hRequest, hdrs, strlen(hdrs), data.GetBuffer(), data.GetLength());
		
		DWORD err = GetLastError();
		if(err != ERROR_IO_PENDING && err != ERROR_SUCCESS)
		{
			::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)WindowsErrorMessage(err).GetString());
			return;
		}

		if(wait)WaitForSingleObject(m_waitHandle, timeout);
	}
	
	int m_tag_cmd, m_tag_id;

	void ManagerUpdate(CString& email, CString& pwd, CSlaveProfile& profile, int tag_cmd)
	{
		m_tag_cmd = tag_cmd;
		m_tag_id = profile.m_ID;
		m_asmxCmd = AsmxCmd::Update;

		m_thisMap.Add(m_hInternetSession, this);

		InternetSetStatusCallback(m_hInternetSession,
                        (INTERNET_STATUS_CALLBACK)InternetCallbackFunction_ASMX);

#ifdef USE_Z560
		m_hConnect = InternetConnect(m_hInternetSession, _T("localhost"), Z560_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#else
		m_hConnect = InternetConnect(m_hInternetSession, _T("www.mytaglist.com"),INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#endif
		m_hRequest = HttpOpenRequest(m_hConnect, _T("POST"), 
			_T("/taglistManager.asmx/ManagerUpdate"),
			NULL, NULL, NULL, INTERNET_FLAG_NO_UI, 2);
		
		static TCHAR hdrs[] =_T("Content-Type: application/json; charset=utf-8");
		data.Format(_T("{email:'%s',password:'%s',tag:{'battery':%f,'signaldBm':%d,'comment':'%s','fired':%s,'doormode':%s,'beeping':%s,'id':%d,'lastComm':%I64u,'name':'%s','typeid':%d,'beepOptionDefault':%d}}"), 
			email.GetString(), pwd.GetString(), profile.batteryVoltageVolt(), 
			profile.signalStrengthIndBm(),
			JsonEscape(profile.m_Comment).GetString(), profile.m_fired?_T("true"):_T("false"), profile.m_hmcOption.door_mode?_T("true"):_T("false"), 
				profile.m_Beeping?_T("true"):_T("false"),
				profile.m_ID, 
				static_cast<__int64>(profile.m_lastCommTime.dwHighDateTime) << 32 | profile.m_lastCommTime.dwLowDateTime, 
				JsonEscape(profile.m_Name).GetString(), profile.m_tagType, profile.m_beepDurationDefault);
		
		HttpSendRequest(m_hRequest, hdrs, strlen(hdrs), data.GetBuffer(), data.GetLength());
		
		DWORD err = GetLastError();
		if(err != ERROR_IO_PENDING && err != ERROR_SUCCESS)
		{
			::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)WindowsErrorMessage(err).GetString());
			return;
		}		
	}

	void CheckUpdate(HWND hwnd, bool showNotification){
		m_showUpdateNotification = showNotification;

		m_thisMap.Add(m_hInternetSession, this);

		InternetSetStatusCallback(m_hInternetSession,
                        (INTERNET_STATUS_CALLBACK)InternetCallbackFunction_CheckUpdate);
		m_hwnd = hwnd;

		DWORD ms, ls;
		CAboutDialog::GetVersionDWORD(ms,ls);

		CString url; 

/*
#ifdef USE_Z560
		url.Format( _T("http://z560/checkupdate.aspx?version_low=%d&version_high=%d"), ls, ms);
#else
		url.Format( _T("http://www.caogadgets.com/checkupdate.aspx?version_low=%d&version_high=%d"), ls, ms);
#endif
		m_hURL = InternetOpenUrl(m_hInternetSession, url, NULL , 0, 
			INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_NO_AUTO_REDIRECT|INTERNET_FLAG_NO_COOKIES|
			INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_RELOAD, (LPARAM)this);
*/
#ifdef USE_Z560
		m_hConnect = InternetConnect(m_hInternetSession, _T("localhost"), Z560_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#else
		m_hConnect = InternetConnect(m_hInternetSession, _T("www.mytaglist.com"),INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#endif
		url.Format(_T("/checkupdate2.aspx?version_low=%d&version_high=%d"), ls, ms);
		m_hRequest = HttpOpenRequest(m_hConnect, _T("GET"), url,
			NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_NO_AUTO_REDIRECT|INTERNET_FLAG_NO_COOKIES|
			INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_UI, 2);
		
		DWORD err = GetLastError();
		if(err!=ERROR_IO_PENDING && err!=0){
			::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)WindowsErrorMessage(GetLastError()).GetString());		
			return;
		}
		HttpSendRequest(m_hRequest, NULL, 0, NULL, 0);

	}
	void SendNotifyEmail(CString& email, CString& tagName, int flag)
	{
		if(!m_enableEmails)
			return;

		m_thisMap.Add(m_hInternetSession, this);

		InternetSetStatusCallback(m_hInternetSession,
                        (INTERNET_STATUS_CALLBACK)InternetCallbackFunction_Emailer);
		
		CString name;
		DWORD len=1024;
		InternetCanonicalizeUrl(tagName, name.GetBuffer(len),&len, ICU_ENCODE_PERCENT);

		CString url; 
/*#ifdef USE_Z560
		url.Format(_T("http://z560/emailer.aspx?to=%s&flag=%d&tagName=%s"), email.GetString(), flag, name.GetString());
#else
		url.Format(_T("http://www.caogadgets.com/emailer.aspx?to=%s&flag=%d&tagName=%s"), email.GetString(), flag, name.GetString());
#endif		
		m_hURL = InternetOpenUrl(m_hInternetSession, url, NULL , 0, 
			INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_NO_AUTO_REDIRECT|INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_RELOAD, (LPARAM)this);
*/

#ifdef USE_Z560
		m_hConnect = InternetConnect(m_hInternetSession, _T("localhost"), Z560_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#else
		m_hConnect = InternetConnect(m_hInternetSession, _T("www.mytaglist.com"),INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
#endif
		url.Format(_T("/emailer.aspx?to=%s&flag=%d&tagName=%s"), email.GetString(), flag, name.GetString());
		m_hRequest = HttpOpenRequest(m_hConnect, _T("GET"), url,
			NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_NO_AUTO_REDIRECT|INTERNET_FLAG_NO_COOKIES|
			INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_UI, 2);

		DWORD err  = GetLastError();
		if(err!=ERROR_IO_PENDING && err != ERROR_SUCCESS){
			CString msg;
			msg.Format(_T("Email not sent: %s"), WindowsErrorMessage(err).GetString());
			::SendMessage(m_hwndStatus, SB_SETTEXT, (255 ), (LPARAM)msg.GetString());
			//MessageBox(NULL,  _T("Internet connection failed, cannot send motion sensor notification email"), _T("Error"), MB_ICONWARNING);
			return;
		}

		HttpSendRequest(m_hRequest, NULL, 0, NULL, 0);
	}
};

static const GUID DEVINTERFACE_HID = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30}};
static const GUID DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, { 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b } };

class CWirelessConfig{
public:
	CWirelessConfig(){}

	float dataRateKbps(){
		return ((float)10e3/29.0/((float)(dataRate&0x7F)+1.0)/(1.0+8.0*float(dataRate>>7) ));
	}

	void update_master_config_cmd(UsbPacket_OUT& packet){
		packet.freq_spi_cmd=0xA000 | this->freq;		// to 0xAF3F

		float DR = dataRateKbps();

		if(DR<=10){
			packet.tx_spi_cmd=0x9820;		// 45k
			packet.rx_spi_cmd = BW_134+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=20){
			packet.tx_spi_cmd=0x9830;		// 60k
			packet.rx_spi_cmd = BW_200+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=30){
			packet.tx_spi_cmd=0x9840;		// 75k
			packet.rx_spi_cmd = BW_200+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=40){
			packet.tx_spi_cmd=0x9850;		// 90k
			packet.rx_spi_cmd = BW_270+LNA_GAIN_0dB+RSSI_m103;
		}else{
			packet.tx_spi_cmd=0x9860;		// 120k
			packet.rx_spi_cmd = BW_340+LNA_GAIN_0dB+RSSI_m103;
		}
		packet.rx_spi_cmd|=((0x90+P16VDI+VDI_FAST)<<8);

		packet.datarate_spi_cmd=0xC600 | this->dataRate;

		int rexp1 = floor( 0.5 + 0x20 / DR * 20);
		if(rexp1>255)rexp1=255;
		if(rexp1<0x10)rexp1=0x10;
		packet.receive_expire1=rexp1;

		int rexp_dqd = floor( 0.5 + 0xD / DR * 20);
		if(rexp_dqd>128)rexp_dqd=128;
		if(rexp_dqd<10)rexp_dqd=10;
		packet.receive_expire_dqd= rexp_dqd ;

		//packet.receive_expire_dqd=0xD;
	}
	void update_slave_flash(Slave_Flash_Section& flash)
	{

		flash.Code_ACTIVE_INTERVAL = this->activeInterval;
		flash.Code_DataRate = this->dataRate;

		flash.Code_FREQ_1 = 0xa0 | (this->freq>>8);
		flash.Code_FREQ_2 = this->freq & 0xff;

		float DR = dataRateKbps();

		int rexp1 = floor( 0.5 + 0x20 / DR * 20);
		if(rexp1>255)rexp1=255;
		if(rexp1<0x10)rexp1=0x10;
		flash.Code_receive_expire1=rexp1;

		int rexp_dqd = floor( 0.5 + 0x10 / DR * 20);
		if(rexp_dqd>128)rexp_dqd=128;
		if(rexp_dqd<10)rexp_dqd=10;
		flash.Code_receive_expire_dqd = rexp_dqd ;

		if(DR < 10)
			flash.Code_PostBack_Retry=10;
		else
			flash.Code_PostBack_Retry=16;

		flash.Code_TX_1 = 0x98;
		flash.Code_RX_1 = 0x90+P16VDI+VDI_FAST;

		if(DR<=10){
			flash.Code_TX_2 = 0x20;		// 45k
			flash.Code_RX_2 = BW_134+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=20){
			flash.Code_TX_2 = 0x30;		// 60k
			flash.Code_RX_2 = BW_200+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=30){
			flash.Code_TX_2 = 0x40;		// 75k
			flash.Code_RX_2 = BW_200+LNA_GAIN_0dB+RSSI_m103;
		}else if(DR<=40){
			flash.Code_TX_2 = 0x50;		// 90k
			flash.Code_RX_2 = BW_270+LNA_GAIN_0dB+RSSI_m103;
		}else{
			flash.Code_TX_2 = 0x60;		// 120k
			flash.Code_RX_2 = BW_340+LNA_GAIN_0dB+RSSI_m103;
		}

		if(DR<10)
			flash.Code_XTAL_Wait=10;
		else
			flash.Code_XTAL_Wait=6;

	}
	float EstimateBatteryLifeInMonths(){
		Slave_Flash_Section flash;
		update_slave_flash(flash);
		return 9.0*(float)flash.Code_ACTIVE_INTERVAL/(float)0x60 * ((float)0x10/(float)flash.Code_receive_expire_dqd);
		//return    9.0 * dataRateKbps()/20.0 * activeInterval/(float)0x60;
	}

	int EstimateRangeInMeter(){
		float DR = dataRateKbps();
		//MyTrace("%.1f kbps\n", DR);
		return (int)floor(0.5 + 30*pow(10.0, (-0.5*10.0*log10(DR/20.0)  )/20.0 ));
	}
	bool operator==(const CWirelessConfig& o1)
	{
		return (o1.dataRate==dataRate && o1.activeInterval == activeInterval && o1.freq == freq);
	}
	CWirelessConfig& operator =(const CWirelessConfig &rhs){
		dataRate=rhs.dataRate;
		activeInterval=rhs.activeInterval;
		freq=rhs.freq;
		return *this;
	}

	U8 dataRate, activeInterval;
	U16 freq;
	void WriteIni(LPCTSTR ini_section_name, CString& fn){	
		CString data;
		data.Format(_T("%d"), dataRate);
		WritePrivateProfileString(ini_section_name, _T("DR"), data, fn);
		data.Format(_T("%d"), activeInterval);
		WritePrivateProfileString(ini_section_name, _T("AI"), data, fn);
		data.Format(_T("%d"), freq);
		WritePrivateProfileString(ini_section_name, _T("Freq"), data, fn);
	}
	void ReadIni(LPCTSTR ini_section_name, CString& fn){
		freq = GetPrivateProfileInt(ini_section_name, _T("Freq"), 0x961, fn);
		activeInterval = GetPrivateProfileInt(ini_section_name, _T("AI"), 0x60, fn);
		dataRate = GetPrivateProfileInt(ini_section_name, _T("DR"), 0x10, fn);
	}
}; 

class CUsbComm{

protected:
	bool m_listening_mode;


public:

	CWirelessConfig m_newConfig, m_oldConfig; 

	void start_ramp_orange_LED(){
		if(m_listening_mode)return;
		m_listening_mode=true;

		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_ORANGE_LIGHT_START_RAMP;
		packet.pwm_max = 250;
		packet.pwm_min = 0;
		packet.pwm_timer_hi = 200;
		packet.pwm_timer_lo = 0;
		SendPacketAsync(&packet);
	}
	void stop_ramp_orange_LED(){
		if(!m_listening_mode)return;
		m_listening_mode=false;

		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_ORANGE_LIGHT_STOP_RAMP;
		SendPacketAsync(&packet);
	}
		
	CUsbComm();
	~CUsbComm();
	


	void StartListening(){
		set_cmd_timeout_ms(1000);
		turn_on_radio(true);
		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_LISTEN_FOR_ONE_EVENT;
		SendPacketAsync(&packet);
		start_ramp_orange_LED();
	}
	void ResumeListening(){
		set_cmd_timeout_ms(1000);
		UsbPacket_OUT packet;	
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd=CMD_LISTEN_FOR_ONE_EVENT;
		SendPacketAsync(&packet);
	}
	void StopListening()
	{
		stop_ramp_orange_LED();
		turn_off_radio();
	}
	bool IsListening(){
		return m_listening_mode;
	}
	
	void turn_on_orange_LED(){
		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_ORANGE_LIGHT_ON;
		SendPacketAsync(&packet);
	}
	void turn_off_orange_LED(){
		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_ORANGE_LIGHT_OFF;
		SendPacketAsync(&packet);
	}

	void fill_in_new_slave_config(Slave_Flash_Section& flash)
	{
		flash.Code_Slave_ID = 0xff;
		flash.Code_Master_ID_1 = 0xB1;
		flash.Code_Master_ID_2 = 0xB2;
		flash.Code_ADC_Run_Interval = 0x20;
		flash.Code_ACTIVE_INTERVAL = 0x20;
		flash.Code_DataRate = 0x10;
		flash.Code_FREQ_1 = 0xa9;
		flash.Code_FREQ_2 = 0xF7;
		flash.Code_MASTER_TO_SLAVE_SYNC_BYTE=MASTER_FIND_NEW_SLAVE_SYNC_BYTE;
		flash.Code_SLAVE_TO_MASTER_SYNC_BYTE=SLAVE_TO_MASTER_SYNC_BYTE;
		flash.Code_receive_expire1=0x40;
		flash.Code_PostBack_Retry=16;
		flash.Code_TX_1 = 0x98;
		flash.Code_TX_2 = 0x40;
		flash.Code_RX_1 = 0x90+P16VDI+VDI_FAST;
		flash.Code_RX_2 = BW_200+LNA_GAIN_0dB+RSSI_m103;
		
		flash.Code_receive_expire_dqd = 0x10;
		flash.Code_XTAL_Wait=6;
		flash.Code_HMC_Transmit_Retry_Wait=0x80;
		flash.Code_HMC_Result_Wait=8;
		flash.Code_BeepStop_Transmit_Retry=0x10;
		flash.Code_AFC_Config=0xA7;
		flash.Code_C2=0x2C;

	}

	void turn_on_radio(bool newConfig){
		if(m_radio_on)return;
		m_radio_on=true;
		
		turn_on_orange_LED();

		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_RADIO_ON;
		
		if(newConfig){
			m_newConfig.update_master_config_cmd(packet);
		}else{
			m_oldConfig.update_master_config_cmd(packet);
		}
		packet.afc_spi_cmd = 0xC4A7;

		//packet.freq=0xAf00;
		packet.rx_sync_byte = SLAVE_TO_MASTER_SYNC_BYTE;
		packet.tx_sync_byte = MASTER_TO_SLAVE_SYNC_BYTE;

		memcpy(packet.rx_master_ids, m_master_ids,3);
		memcpy(packet.tx_master_ids, m_master_ids,3);
		SendPacketAsync(&packet);
	}
	void fill_in_radio_related_config(Slave_Flash_Section& flash, U8 id2)
	{
		//flash.Code_FREQ_1 = 0xa0 | (id2>>4);
		//flash.Code_FREQ_2 = (id2 & 0xf) << 4;

//		flash.Code_FREQ_1 = 0xa9;
//		flash.Code_FREQ_2 = 0x60;

		flash.Code_ADC_Run_Interval = 0x10;
//		flash.Code_ACTIVE_INTERVAL = 0x60;
		flash.Code_MASTER_TO_SLAVE_SYNC_BYTE=MASTER_TO_SLAVE_SYNC_BYTE;
		flash.Code_SLAVE_TO_MASTER_SYNC_BYTE=SLAVE_TO_MASTER_SYNC_BYTE;
		
		
//		flash.Code_PostBack_Retry=32;

//		flash.Code_TX_1 = 0x98;
//		flash.Code_TX_2 = 0x40;
//		flash.Code_RX_1 = 0x90+P16VDI+VDI_FAST;
//		flash.Code_RX_2 = BW_200+LNA_GAIN_0dB+RSSI_m103;
//		flash.Code_DataRate = 0x10;		// 10.5kbps
	
//		flash.Code_receive_expire1=0x40;
//		flash.Code_receive_expire_dqd = 0x10;
		flash.Code_XTAL_Wait=6;
		flash.Code_HMC_Transmit_Retry_Wait=0x60;
		flash.Code_HMC_Result_Wait=8;
		flash.Code_BeepStop_Transmit_Retry=0x10;
		flash.Code_AFC_Config=0xA7;
		flash.Code_C2=0x2C;

		m_newConfig.update_slave_flash(flash);
	}

	void turn_on_radio_for_finding_new_slave(){
		//if(m_radio_on)return;
		m_radio_on=true;

		turn_on_orange_LED();

		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_RADIO_ON;
		
		packet.freq_spi_cmd=0xA9F7;
		packet.tx_spi_cmd=0x9840;
		packet.rx_spi_cmd=((0x90+P16VDI+VDI_FAST)<<8)|(BW_200+LNA_GAIN_0dB+RSSI_m103);
		packet.datarate_spi_cmd=0xC610;
		packet.afc_spi_cmd = 0xC4A7;

		packet.tx_sync_byte = MASTER_FIND_NEW_SLAVE_SYNC_BYTE;  //MASTER_TO_SLAVE_SYNC_BYTE;
		packet.rx_sync_byte = SLAVE_TO_MASTER_SYNC_BYTE;
		packet.receive_expire1=0x40;
		packet.receive_expire_dqd=0xD;

		packet.rx_master_ids[0]=0xB1;
		packet.rx_master_ids[1]=0xB2;
		packet.rx_master_ids[2]=0xB3;
		memcpy(packet.tx_master_ids, packet.rx_master_ids,3);
		SendPacketAsync(&packet);
	}
	void set_cmd_timeout(bool newConfig){
		if(newConfig)
			set_cmd_timeout_ms((int)(m_newConfig.activeInterval*35*1.2));
		else
			set_cmd_timeout_ms((int)(m_oldConfig.activeInterval*35*1.2));
	}
	void set_cmd_timeout_ms(int timeout_in_ms){
		// T6
		int timeout = (float)timeout_in_ms/21.8/2;
		//int timeout = (float)timeout_in_ms/21.8;
		if(timeout>255)timeout=255;
		timeout = -timeout;
		
		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_TIMEOUT;
		packet.cmd_timeout=(U8)timeout;
		SendPacketAsync(&packet);
	}

	void turn_off_radio(){
		if(m_listening_mode)return;

		//if(!m_radio_on)return;
		m_radio_on=false;

		turn_off_orange_LED();

		UsbPacket_OUT packet;
		ZeroMemory(&packet, sizeof(packet));
		packet.cmd = CMD_CONFIG;
		packet.config_cmd = CONFIG_CMD_RADIO_OFF;
		SendPacketAsync(&packet);
	}

	void PushNotificationWnd(HWND hwnd);
	void PopNotificationWnd();

	bool IsAttached() { return m_attached; }
	bool m_anotherStickDetected;

	void detachDevice();

	DWORD attachDevice(CString DeviceIDToFind = _T("Vid_04D8&Pid_018D"));
	//DWORD attachDevice(CString DeviceIDToFind = _T("Vid_04D8&Pid_007C"));
	bool SendPacketAsync(UsbPacket_OUT* pPacket);
	
	unsigned char m_master_ids[3];
	
protected:
	bool m_radio_on;

	struct NotificationWnd{
		NotificationWnd() : m_hdevnotify_hid(NULL), m_hwnd(NULL) {}
		HDEVNOTIFY m_hdevnotify_hid;
		HWND m_hwnd;
	};

	struct Notification : std::list<NotificationWnd>
	{
		BOOL PostMessage(UINT msg, WPARAM wp, LPARAM lp){
			if(!empty())
				return ::PostMessage(back().m_hwnd, msg, wp, lp);	
			else
				return FALSE;
		}
	} m_notification_wnd_stack;
	
	bool m_attached;
	HANDLE m_usbHandle, m_hThread;
	DWORD m_idThread;
	//WORD freq;

	struct OVERLAPPED_ex{
		OVERLAPPED ov;
		//DWORD written;
		CUsbComm *obj;
		BYTE packet_start;
		UsbPacket_OUT packet;
	};

	void decOutstandingWrites(){
		if(--m_outstandingWrites == 0)
			SetEvent(m_hEventAllWritesDone);
	}
	void incOutstandingWrites(){
		ResetEvent(m_hEventAllWritesDone);
		m_outstandingWrites++;
	}
	DWORD waitForAllWritesDone(DWORD timeout){
		return WaitForSingleObjectEx(m_hEventAllWritesDone, timeout, TRUE);
	}
	HANDLE m_hEventAllWritesDone, m_hEventThreadReady;
	DWORD m_outstandingWrites;
	
	static void CALLBACK WriteCompletionRoutine(DWORD err, DWORD bytesWritten, OVERLAPPED* overlapped);
	static DWORD CALLBACK ThreadProc(CUsbComm* obj);
};



class CListSlaveData : public CListImpl< CListSlaveData >
{
	typedef CListImpl< CListSlaveData > baseClass;
public:
	DECLARE_WND_CLASS( _T( "UserList" ) )
	enum Columns {  colName, /*colAutoBeep,*/ colBeep, colSignalStrength, colBat, colAge, colComment };
	
	CListArray < CSlaveProfile > m_aSlaves;

protected:
	CImageList m_ilItemImages32, m_ilItemImages24, m_ilItemImages16;

	COLORREF m_rgbItemText_Disabled;

public:
	typedef std::set<CString> EmailSet;
	EmailSet m_emailAddrs;
	
	CString ini_fn, ini_fn_usb;
	CListSlaveData(CString _ini_fn){
		if(_ini_fn.IsEmpty()){
			GetModuleFileName(NULL, ini_fn_usb.GetBuffer(1024),1019);
			ini_fn_usb.ReleaseBuffer();
			ini_fn_usb+=_T(".data");
		}else{
			ini_fn_usb=_ini_fn;
		}
		GetTempPath(1024, ini_fn.GetBuffer(1024));
		ini_fn.ReleaseBuffer();
		ini_fn.TrimRight(_T('\\'));
		ini_fn.Format(_T("%s\\TagManagerDataCache_PID%x.data"), ini_fn, GetCurrentProcessId());
		CopyFile(ini_fn_usb, ini_fn, FALSE);
	}
	~CListSlaveData(){
		while(!FlushIni()){
			if(IDCANCEL==MessageBox(
				_T("Please insert the USB TagManager stick and press OK. The tag list data still needs to be saved onto the USB stick. Press cancel to exit the application anyway, but there may be unsaved tag information. "), 
				_T("Wireless Tag Manager"), MB_ICONWARNING|MB_OKCANCEL))
				return;
		}
		DeleteFile(ini_fn);
	}
	/*
	// search through Tag Manager USB stick, open one that is not share locked. 
	// called when WM_DEVICECHANGE indicating a new Tag Manager disk may have become available
	bool attachDataFile(DWORD unitmask){
		if(!ini_fn.IsEmpty())return ERROR_SUCCESS;
		for(i=0;i<26;i++){
			if(unitmask>>i & 0x1){
				ini_fn.Format(_T("%c:\\"), _T('A')+i);
				CString volume_name; 
				DWORD maxlen, flags;
				GetVolumeInformation(ini_fn, volume_name.GetBuffer(MAX_PATH+1), MAX_PATH+1, NULL, &maxlen, &flags,NULL,0);
				volume_name.ReleaseBuffer();
				if(volume_name.Compare(_T("TagManager"))==0){
					ini_fn.Append(_T("WirelessTagManager.exe.data"));
					m_hLockIni = CreateFile(ini_fn, GENERIC_READ|GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, 0);
					if(m_hLockIni != INVALID_HANDLE_VALUE){
						ReadFile();
						if(LockFile(m_hLockIni, 0,0,-1,-1)){
							return true;
						}
					}
				}
			}
		}
		ini_fn.Empty();
		return false;
	}
	// called when attachDevice USB fails, indicating USB stick has been removed. 
	void detachDataFile(){
		ini_fn.Empty();
		UnlockFile(m_hLockIni, 0,0,-1,-1);
		CloseHandle(m_hLockIni);
	}
	*/
	
	void MarkSlaveDeadOlderThan(DWORD age){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			CSlaveProfile& profile = m_aSlaves[i];
			if(profile.secondsOld() > age){
				if(profile.m_Alive){
					profile.m_Alive= false;
					if(profile.m_beeperOption.beep_pc){
						
						//DWORD oldvol;
						//waveOutGetVolume(NULL, &oldvol);

						waveOutSetVolume(NULL, profile.m_beeperOption.beep_pc_vol<<8 | profile.m_beeperOption.beep_pc_vol<<24);
						//PlaySound(_T("SystemNotification"), NULL, SND_ASYNC);
						PlaySound(_T("SystemHand"), NULL, SND_ALIAS|SND_ASYNC);
						
						//waveOutSetVolume(NULL, oldvol);

						SelectItem(i);
					}
					if(profile.m_beeperOption.send_email){
						CCaoGadgetsDotCom *web = new CCaoGadgetsDotCom();
						web->SendNotifyEmail(profile.m_beeperOption.email, profile.m_Name, 1);
					}
					InvalidateItem(i);
				}
			}
		}
	}
	int GetMinimumID(){
		int id=256;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			if(m_aSlaves[i].m_ID<id)
				id=m_aSlaves[i].m_ID;
		}
		return id;
	}

	int AssignNewID(){
		int id=0;
		if(m_aSlaves.GetSize()==0)return 0;
		
		/*std::set<U8> idset;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			idset.insert(m_aSlaves[i].m_ID);
		}

		for(int i=0;i<254;i++){
			if(idset.find(slave_id_choices[i])==idset.end())
				return slave_id_choices[i];
		}
		return 0xff;*/
		
		
		CListArray < CSlaveProfile > slaves_copy = m_aSlaves;
		slaves_copy.Sort( CompareItemId());
		for(int i=0;i<slaves_copy.GetSize(); i++){
			if(slaves_copy[i].m_ID==id){
				id++;
			}else{
				break;
			}
		}
		return id;
	}
	int GetMaximumID(){
		int id=0;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			if(m_aSlaves[i].m_ID>id)
				id=m_aSlaves[i].m_ID;
		}
		return id;
	}
	int GetNumSlaveWithIDRange(int begin, int end){
		int num=0;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			if(m_aSlaves[i].m_ID>=begin && m_aSlaves[i].m_ID<=end)
				num++;
		}
		return num;
	}

	void WriteFile(){		
		
		for(int i=0;i<256;i++){
			CString section; section.Format(_T("Tag%d"), i);
			WritePrivateProfileString(section, NULL, NULL, ini_fn);
		}

		for(int i=0;i<m_aSlaves.GetSize();i++){
			m_aSlaves[i].WriteIni(i, ini_fn);
		}
	}
	bool FlushIni(){
		if(CopyFile(ini_fn, ini_fn_usb, FALSE)){
			MyTrace(_T("Flush Ini SUCCESS.\n"));
			return true;
		}
		else{
			MyTrace(_T("Flush Ini FAILED err=%d.\n"), GetLastError());
			return false;
		}
	}
	void ReadFile(){
		CopyFile(ini_fn_usb, ini_fn, FALSE);

		m_aSlaves.RemoveAll();
				
		CSlaveProfile profile;
		for(int i=0;i<256;i++){
			if(!profile.ReadIni(i, ini_fn))break;
			if(!profile.m_hmcOption.email.IsEmpty())
				m_emailAddrs.insert(profile.m_hmcOption.email);
			m_aSlaves.InsertAt(i,profile);
		}		
	}
	void ApplyBeeperOptionToAll(CSlaveProfile::BeeperOption option){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			m_aSlaves[i].m_beeperOption = option;
		}
	}
	void ApplyHmcOptionToAll(CSlaveProfile::HmcOption option){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			CSlaveProfile& profile = m_aSlaves[i];
			if(profile.m_tagType == CSlaveProfile::tagHmc || profile.m_tagType == CSlaveProfile::tagHmcArmed)
				profile.m_hmcOption = option;
		}
	}
	bool AnyArmedOrBeepingTags(){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			CSlaveProfile& profile = m_aSlaves[i];
			if(profile.m_tagType == CSlaveProfile::tagHmcArmed && profile.m_Alive) return true;
			if(profile.m_Beeping && profile.m_Alive) return true;
		}
		return false;
	}
	std::map<int, int> getAllArmedTags(){
		std::map<int, int> ret;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			CSlaveProfile& profile = m_aSlaves[i];
			if(profile.m_tagType == CSlaveProfile::tagHmcArmed )
				ret[i]=profile.m_ID;
		}
		return ret;		
	}
	std::map<int, int> getAllTags(){
		std::map<int, int> ret;
		for(int i=0;i<m_aSlaves.GetSize();i++){
			CSlaveProfile& profile = m_aSlaves[i];
			ret[i]=profile.m_ID;
		}
		return ret;		
	}

	BOOL LoadSettings(){
		
		m_rgbItemText_Disabled = GetSysColor( COLOR_GRAYTEXT );
		
		return CListImpl<CListSlaveData>::LoadSettings();

	}

	BOOL SetBestIconAndFontSize(){
		
		// get system message font
		CLogFont logFont;
		logFont.SetMessageBoxFont();
		
		CRect rcClient;
		GetClientRect( rcClient );
		
		int average_height = rcClient.Height() / (m_aSlaves.GetSize()+1);
		if(average_height < 28){
			if(m_ilItemImages16 == m_ilItemImages)return TRUE;
			SetImageList( m_ilItemImages16 );
			logFont.lfHeight = 14;
		}else if(average_height < 38){
			if(m_ilItemImages24 == m_ilItemImages)return TRUE;
			SetImageList( m_ilItemImages24 );
			logFont.lfHeight = 20;
		}else{
			if(m_ilItemImages32 == m_ilItemImages)return TRUE;
			SetImageList( m_ilItemImages32 );
			logFont.lfHeight = 30;
		}
		
		logFont.lfWeight = FW_THIN;

		if ( !m_fntListFont.IsNull() ){
			m_fntListFont.DeleteObject();
		}
		
		if ( m_fntListFont.CreateFontIndirect( &logFont ) == NULL )
			return FALSE;

		// get system underline font
		logFont.lfUnderline = BYTE(TRUE);
		if ( !m_fntUnderlineFont.IsNull() ){
			m_fntUnderlineFont.DeleteObject();
		}
		if ( m_fntUnderlineFont.CreateFontIndirect( &logFont ) == NULL )
			return FALSE;

		CSize sizeExtent = getFontExtent(m_fntListFont );

		// has system font changed
		m_nItemHeight = sizeExtent.cy + ITEM_HEIGHT_MARGIN;
		
		for(int i=0;i<this->GetColumnCount();i++){
			this->AutoSizeColumn(i);
		}
		return Invalidate();
	}
	
	virtual void OnSize( UINT nType, CSize size )
	{
		CListImpl< CListSlaveData >::OnSize(nType, size);
		SetBestIconAndFontSize();
	}
	
	BOOL InvalidateItem( int nItem, int nSubItem = NULL_SUBITEM )
	{
		//m_aSlaves[nItem].WriteIni(nItem, ini_fn);

		return baseClass::InvalidateItem(nItem, nSubItem);
	}

	BOOL Initialise()
	{
		m_nAnimation=0;
		if ( !m_ilItemImages32.CreateFromImage( IDB_EXAMPLE32, 32, 0, RGB( 255, 0, 255 ), IMAGE_BITMAP, LR_CREATEDIBSECTION ) )
			return FALSE;
		if ( !m_ilItemImages24.CreateFromImage( IDB_EXAMPLE24, 24, 0, RGB( 255, 0, 255 ), IMAGE_BITMAP, LR_CREATEDIBSECTION ) )
			return FALSE;
		if ( !m_ilItemImages16.CreateFromImage( IDB_EXAMPLE16, 16, 0, RGB( 255, 0, 255 ), IMAGE_BITMAP, LR_CREATEDIBSECTION ) )
			return FALSE;
		
		AddColumn( _T( "Name" ), 150 , ITEM_IMAGE_NONE, FALSE, ITEM_FORMAT_EDIT );
		
		//AddColumn( _T("Auto Beep"), 50, ITEM_IMAGE_NONE, TRUE, ITEM_FORMAT_CHECKBOX);

		AddColumn( _T( "Beep" ), 100, ITEM_IMAGE_NONE, FALSE, ITEM_FORMAT_HYPERLINK );
		AddColumn( _T( "Signal Strength" ), 70, ITEM_IMAGE_NONE, FALSE );
		AddColumn( _T( "Battery" ), 70, ITEM_IMAGE_NONE, FALSE, ITEM_FORMAT_PROGRESS );
		AddColumn( _T( "Last Communication" ), 100, ITEM_IMAGE_NONE, FALSE);
		AddColumn( _T( "Comment                         " ), 200, ITEM_IMAGE_NONE, FALSE, ITEM_FORMAT_EDIT );
		

		if(!SetBestIconAndFontSize())return FALSE;

		return CListImpl< CListSlaveData >::Initialise();
	}
	
	int GetItemCount() // required by CListImpl
	{
		return m_aSlaves.GetSize();
	}

	BOOL SetItemText( int nItem, int nSubItem, LPCTSTR lpszText )
	{		
		if ( nItem < 0 || nItem >= GetItemCount() ) 
			return FALSE;
		if ( (Columns)nSubItem == colName)
			return SetName(nItem, lpszText);
		else if ( (Columns)nSubItem == colComment)
			return SetComment(nItem, lpszText);
		else return FALSE;
	}

	UINT GetItemFlags( int nItem, int nSubItem )
	{
		UINT flags = GetColumnFlags( IndexToOrder( nSubItem ) );

		if((Columns)nSubItem == colBat){
			CSlaveProfile *profile;
			if ( GetSlave( nItem, profile ) ){
				if(profile->percentBatteryRemaining()<30){
					flags |= ITEM_FLAGS_PROGRESS_WARN;
				}
			}
		}
		return flags;
	}

	BOOL GetSlaveFromId(int ID, CSlaveProfile* &profile, int& nItem ){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			if(m_aSlaves[i].m_ID==ID){
				profile=&m_aSlaves[i];
				nItem = i;
				return TRUE;
			}
		}
		return FALSE;
	}

	BOOL DeleteItem( int nItem )
	{
		if ( nItem < 0 || nItem >= GetItemCount() ) 
			return FALSE;
		return m_aSlaves.RemoveAt( nItem ) ? baseClass::DeleteItem( nItem ) : FALSE;
	}
	
	BOOL GetSlave( int nItem, CSlaveProfile* &profile )
	{
		if ( nItem < 0 || nItem >= GetItemCount() ) 
			return FALSE;
		profile = &m_aSlaves[ nItem ];
		return TRUE;
	}
	
	BOOL SetName(int nItem, CString name){
		m_aSlaves[nItem].m_Name = name;
		return TRUE;
	}
	BOOL SetComment(int nItem, CString comment){
		m_aSlaves[nItem].m_Comment=comment;
		return TRUE;
	}
	void OnTimer( UINT nIDEvent)
	{
		if(nIDEvent == ITEM_ANIMATE_TIMER){
			
			for(int i=0;i<m_aSlaves.GetSize();i++){

				if(m_aSlaves[i].m_animationPhase!=-1){
					m_aSlaves[i].m_animationPhase=(m_aSlaves[i].m_animationPhase+1)%2;
					InvalidateItem(i);
				}
			}

		}else{
			CListImpl< CListSlaveData >::OnTimer(nIDEvent);
		}
	}
	
	int m_nAnimation;
	//ATL::CCriticalSection m_lock;
	
	
	void StartAnimateAll(){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			StartAnimate(i);
		}			
	}
	void StopAnimateAll(){
		for(int i=0;i<m_aSlaves.GetSize();i++){
			StopAnimation(i, false);
		}
		Invalidate();
	}

	void StartAnimate( int nItem)
	{
		if(m_nAnimation==0)
			SetTimer(ITEM_ANIMATE_TIMER, 500);
		if(m_aSlaves[nItem].m_animationPhase==-1){
			m_nAnimation++;
			m_aSlaves[nItem].m_animationPhase=0;
		}
	}
	void StopAnimation(int nItem, bool invalidate=true){
		
		if(m_aSlaves[nItem].m_animationPhase==-1)return;

		m_nAnimation--;
		if(m_nAnimation==0)
			KillTimer(ITEM_ANIMATE_TIMER);
		m_aSlaves[nItem].m_animationPhase=-1;
		if(invalidate)
			InvalidateItem(nItem);
	}
	bool IsAnimating(int nItem){
		return m_aSlaves[nItem].m_animationPhase!=-1;
	}
/*
	void SetBeep( int nItem, bool beep )
	{
		if(m_aSlaves[nItem].m_Beeping != beep){
			m_aSlaves[nItem].m_Beeping=beep;
			InvalidateItem(nItem);
		}
	}
	void SetAlive( int nItem, bool alive )
	{
		if(m_aSlaves[nItem].m_Alive != alive){
			m_aSlaves[nItem].m_Alive=alive;
			InvalidateItem(nItem);
		}
	}
*/	
	CString GetItemText( int nItem, int nSubItem ) // required by CListImpl
	{
		CSlaveProfile *profile;
		if ( !GetSlave( nItem, profile ) )
			return _T( "" );
		switch ( (Columns)nSubItem )
		{
			case colBeep: return profile->m_animationPhase==-1? 
							  ( profile->m_migrationPending ? _T("Migrate Wireless Config") : 
								((profile->m_fired&&!profile->m_hmcOption.door_mode) ? _T("Reset") : 
									(profile->m_Beeping?  _T("Stop Beep") : _T("Beep It!")))) : _T("Finding...");
			case colName:	return profile->m_Name;
			case colComment:	return profile->m_Comment;
			case colBat:	return  profile->percentBatteryRemainingT();
			case colSignalStrength: return profile->signalStrengthIndBmT();
			case colAge: return profile->ageText();
			//case colAutoBeep: return profile->m_autoBeep ? _T("1") : _T("0");
		}
		return _T( "" );
	}
	
	// 
	BOOL GetItemColours( int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText )
	{
		CSlaveProfile *profile;
		GetSlave( nItem, profile );
		
		/*if(!profile->m_fired)
			rgbBackground = m_rgbBackground;
		else
			rgbBackground = RGB(255,0,0);
		*/
		rgbBackground = m_rgbBackground;
		if(IsWindowEnabled()){
			rgbText = profile->m_migrationPending ? m_rgbItemText_Disabled : (profile->m_fired ? RGB(215,0,0) : m_rgbItemText);
		}else{
			rgbText = m_rgbItemText_Disabled;
		}
		return TRUE;
	}

	int GetItemImage( int nItem, int nSubItem ) // overrides CListImpl::GetItemImage
	{
		CSlaveProfile *profile;
		
		if ( (Columns)nSubItem == colName ){
			if ( GetSlave( nItem, profile) ){
				if(!profile->m_Alive)
					return 11;
				else if(profile->m_fired)
					return 3;
				else return profile->m_tagType;
			}
		}else if((Columns)nSubItem == colBeep ){
			if ( GetSlave( nItem, profile) ){
				if(profile->m_animationPhase!=-1){
					return 4+profile->m_animationPhase;
				}
				
				return profile->m_Beeping? 1:0;
			}
		}else if((Columns)nSubItem == colSignalStrength ){
			if ( GetSlave( nItem, profile) ){
				if(profile->signalStrengthIndBm()<=-110) return ITEM_IMAGE_NONE;
				else if(profile->signalStrengthIndBm()<-100) return 6;
				else if(profile->signalStrengthIndBm()<-90) return 7;
				else if(profile->signalStrengthIndBm()<-80) return 8;
				else if(profile->signalStrengthIndBm()<-70) return 9;
				else return 10;
			}
		}
		return ITEM_IMAGE_NONE;		
	}
	
	UINT GetItemFormat( int nItem, int nSubItem )
	{
		CSlaveProfile *profile;
		if((Columns)nSubItem == colBeep ){
			if ( GetSlave( nItem, profile) ){
				if(profile->m_animationPhase!=-1)return ITEM_FORMAT_NONE;
			}
		}
		return baseClass::GetItemFormat(nItem, nSubItem );
	}
	
	CString GetItemToolTip( int nItem, int nSubItem )
	{
		CSlaveProfile *profile;
		
		if ( (Columns)nSubItem == colName ){
			if ( GetSlave( nItem, profile) ){
				if(!profile->m_Alive)
					return _T("Tag is not responding...");
				if(profile->m_tagType==CSlaveProfile::TagType::tagBeeper)
					return _T("A basic beeper tag");
				if(profile->m_tagType==CSlaveProfile::TagType::tagHmc)
					return _T("A disarmed motion sensor tag");
				if(profile->m_tagType==CSlaveProfile::TagType::tagHmcArmed)
					return _T("An armed motion sensor tag");
			}
		}else if((Columns)nSubItem == colBeep ){
			if ( GetSlave( nItem, profile) ){
				if(profile->m_migrationPending)
					return _T("Try again to program this tag to the new wireless configuration.");
				if(profile->m_fired && !profile->m_hmcOption.door_mode)
					return _T("Reset fired state");

				return profile->m_Beeping? _T("Click to stop beep"):_T("Click to make this item beep!");
			}
		}
		/*else if((Columns)nSubItem == colAutoBeep ){
			return _T("Check to make beep as soon as Tag Manager is started");
		}*/
		else if((Columns)nSubItem == colBat ){
			if ( GetSlave( nItem, profile) ){
				float v = profile->batteryVoltageVolt();
				CString t; t.Format(_T("Battery voltage: %d.%d V"), (int)floorf(v), (int)((v-floorf(v))*100) );
				return t;
			}
		}else if((Columns)nSubItem == colComment ){
			return _T("Double click to edit memo/comment");
		}
		return CListImpl< CListSlaveData >::GetItemToolTip(nItem,nSubItem);
	}
	
	void ReverseItems() // overrides CListImpl::ReverseItems
	{
		m_aSlaves.Reverse();
	}
	
	class CompareItem
	{
	public:
		CompareItem( Columns colColumn ) : m_colColumn( colColumn ) {}
		inline bool operator() ( const CSlaveProfile& p1, const CSlaveProfile& p2)
		{
			switch ( m_colColumn )
			{
				case colAge: return (p1.secondsOld() > p2.secondsOld());
				//case colName:	return ( p1.m_Name.Compare( p2.m_Name) < 0 );
				case colName:	return ( p1.m_ID < p2.m_ID );
				case colComment:	return ( p1.m_Comment.Compare( p2.m_Comment ) < 0 );
				case colBeep:	return ( p1.m_Beeping > p2.m_Beeping);
				case colBat: return (p1.m_batADCout < p2.m_batADCout);
				case colSignalStrength: return (p1.m_arssiADCout > p2.m_arssiADCout);
				//case colAutoBeep: return p1.m_autoBeep > p2.m_autoBeep;
			}
			return false;
		}
		
	protected:
		Columns m_colColumn;
	};

	class CompareItemId
	{
	public:
		CompareItemId( ) {}
		inline bool operator() ( const CSlaveProfile& p1, const CSlaveProfile& p2)
		{
			return p1.m_ID < p2.m_ID;
		}
	};
	void SortItems( int nColumn, BOOL bAscending ) // overrides CListImpl::SortItems
	{
		m_aSlaves.Sort( CompareItem( (Columns)nColumn ) );
	}
};

#endif
